/*
 * Copyright 2007-2009 Perttu "celeron55" Ahola <celeron55@gmail.com>
 *
 * This file is part of jukumagic.
 *
 * jukumagic is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * jukumagic is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with jukumagic.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "common.h"
#include "netlist.h"
#include "strfnd.h"
#include "board.h"

#include <iostream>
#include <fstream>
#include <string>
#include <wx/string.h>
#include <algorithm>

Netlist::Netlist()
{
	//std::cout<<"Netlist"<<std::endl;
}

Netlist::~Netlist()
{
	//std::cout<<"~Netlist"<<std::endl;
	for(std::vector<Net*>::iterator i = m_nets.begin(); i != m_nets.end(); i++){
		delete (*i);
	}
	for(std::vector<NetComponent*>::iterator i = m_components.begin(); i != m_components.end(); i++){
		delete (*i);
	}
}

void Netlist::WriteXML(TiXmlNode* root)
{
	//if(DP)std::cout<<"Netlist::WriteXML()"<<std::endl;
    TiXmlElement *element = new TiXmlElement("Netlist");
    root->LinkEndChild(element);
    TiXmlElement *nets_element = new TiXmlElement("Nets");
    element->LinkEndChild(nets_element);
    TiXmlElement *components_element = new TiXmlElement("Components");
    element->LinkEndChild(components_element);

	for(std::vector<Net*>::iterator i = m_nets.begin(); i != m_nets.end(); i++){
		(*i)->WriteXML(nets_element);
	}

	for(std::vector<NetComponent*>::iterator i = m_components.begin(); i != m_components.end(); i++){
		(*i)->WriteXML(components_element);
	}

	JukuObject::WriteBaseXML(element);
}

void Netlist::ReadBaseXML(TiXmlElement *element)
{
	if(element == NULL) return;

	for(TiXmlElement *e = TiXmlHandle(element).FirstChildElement("Nets").FirstChildElement().Element(); e; e = e->NextSiblingElement())
	{
		//std::cout<<"value="<<e->Value()<<std::endl;
		Net *net = (Net*)Net::ReadFromXMLElement(e);
		m_nets.push_back(net);
	}
	
	//usually not needed but good to call anyway
	SortNets();

	for(TiXmlElement *e = TiXmlHandle(element).FirstChildElement("Components").FirstChildElement().Element(); e; e = e->NextSiblingElement())
	{
		//std::cout<<"value="<<e->Value()<<std::endl;
		NetComponent *netc = (NetComponent*)NetComponent::ReadFromXMLElement(e);
		m_components.push_back(netc);
	}
	
	JukuObject::ReadBaseXML(element);
}

JukuObject* Netlist::ReadFromXMLElement(TiXmlElement* element)
{
    Netlist *netlist = new Netlist();

	netlist->ReadBaseXML(element);

    return netlist;
}

Net* Netlist::GetNetByPin(const NetPin &p)
{
	for(std::vector<Net*>::iterator i = m_nets.begin(); i != m_nets.end(); i++){
		Net *net = (Net*)(*i);
		if(net->HavePin(p)) return net;
	}
	return NULL;
}

#define LINEBUF_SIZE 10000

bool Netlist::LoadFromSpiceFile(const char *filepath)
{
	m_nets.clear();
	//m_components.clear();
	bool components_was_empty = m_components.empty();
	
	std::ifstream file(filepath);
	if(!file.good()){
		//if(DP)std::cout<<"!file.good()"<<std::endl;
		return false;
	}

	std::list<NetPin> pins;
	std::list<wxString> netnames;

	char buf[LINEBUF_SIZE];
	while(!file.eof()){
		file.getline(buf, LINEBUF_SIZE);

		//std::cout<<"got line \""<<buf<<"\""<<std::endl;

		bool is_comment = false;

		for(int i=0; i<LINEBUF_SIZE; i++){
			if(buf[i] == '*') is_comment = true;
			if(buf[i] != ' ' && buf[i] != '\t') break;
		}

		if(is_comment) continue;

		strfnd f(buf);
		//TODO: check that this works without trim
		std::string refdes = f.next(" ");
		//std::string refdes = trim(f.next(" "));
		std::string value;

		if(refdes == ".END") break;
		if(refdes == "") continue;

		wxString refdes_wx(refdes.c_str(), wxConvUTF8);

		for(int i=1;;i++){
			int p = f.getp();
			//wxString s(trim(f.next(" ")).c_str(), wxConvUTF8);
			wxString s(f.next(" ").c_str(), wxConvUTF8);
			if(!f.atend() && s[0] != '<'){
				//pin
				
				wxString num_wx;
				num_wx << i;

				NetPin p(refdes_wx, num_wx);

				std::list<wxString>::iterator i = find(netnames.begin(), netnames.end(), s);
				if(i == netnames.end()) netnames.push_back(s);
				
				p.m_netid = s;

				/*std::cout<<"adding pin "<<p.m_refdes.fn_str()<<":"<<p.m_num.fn_str()
						<<" netid="<<p.m_netid.fn_str()<<std::endl;*/
						
				pins.push_back(p);
			}
			else{
				//value
				f.moveto(p);
				value = f.next(">");
				if(s[0]=='<') value += ">";
				break;
			}
		}

		//std::cout<<"component: refdes="<<refdes<<", value="<<value<<std::endl;
		
		if(components_was_empty){
			wxString value_wx(value.c_str(), wxConvUTF8);
			
			NetComponent *netc = new NetComponent();
			netc->m_refdes = refdes_wx;
			if(value_wx != wxT("<No valid value attribute found>"))
				netc->m_value = value_wx;
			m_components.push_back(netc);
		}
	}

	for(std::list<wxString>::iterator i = netnames.begin(); i != netnames.end(); i++){
		std::cout<<"net "<<(*i).fn_str()<<std::endl;
		Net *net = new Net();
		net->m_name = (*i);
		for(std::list<NetPin>::iterator k = pins.begin(); k != pins.end(); k++){
			if((*k).m_netid == (*i)){
				net->m_pins.push_back(*k);
			}
		}
		m_nets.push_back(net);
	}

	file.close();
	
	SortNets();

	return true;
}

enum PartslistColType
{
	COL_UNKNOWN,
	COL_DEVICE,
	COL_VALUE,
	COL_FOOTPRINT,
	COL_QUANTITY,
	COL_REFDES
};

PartslistColType GetTypeFromString(const std::string &s)
{
	if(s=="device")    return COL_DEVICE;
	if(s=="value")     return COL_VALUE;
	if(s=="footprint") return COL_FOOTPRINT;
	if(s=="quantity")  return COL_QUANTITY;
	if(s=="refdes")    return COL_REFDES;
	return COL_UNKNOWN;
}

bool Netlist::LoadFromPartslistFile(const char *filepath)
{
	std::cout<<"Netlist::LoadFromPartslistFile()"<<std::endl;
	std::ifstream file(filepath);
	if(!file.good()){
		//if(DP)std::cout<<"!file.good()"<<std::endl;
		return false;
	}

	std::vector<PartslistColType> coltypes;

	m_components.clear();

	char buf[LINEBUF_SIZE];
	while(!file.eof()){
		file.getline(buf, LINEBUF_SIZE);

		std::cout<<"got line \""<<buf<<"\""<<std::endl;

		bool is_comment = false;

		for(int i=0; i<LINEBUF_SIZE; i++){
			if(buf[i] == '*') is_comment = true;
			if(buf[i] != ' ' && buf[i] != '\t') break;
		}

		if(is_comment) continue;

		strfnd f(buf);
		//TODO: check that this works without trim
		std::string s = f.next("\t");
		//s = s.substr(0, s.size()-1);
		//std::string s = trim(f.next(" "));

		if(s == ".END") break;
		if(s == "") continue;
		if(s == ".START") continue;

		if(s.substr(0,2)==".."){
			f.moveto(0);
			std::string t = f.next("\t").substr(2);
			for(;;){
				std::cout<<"coltype \""<<t<<"\""<<std::endl;
				PartslistColType type = GetTypeFromString(t);
				coltypes.push_back(type);
				if(f.atend()) break;
				//t = trim(f.next("\t"));
				t = f.next("\t");
			}
			std::cout<<"got coltypes"<<std::endl;
			continue;
		}

		f.moveto(0);

		//std::cout<<"a normal row"<<std::endl;
		
		//now we should have a normal row
		std::string s_device;
		std::string s_value;
		std::string s_footprint;
		std::string s_quantity;
		std::string s_refdes;
		for(unsigned int i=0; i<coltypes.size(); i++){
			//s = trim(f.next("\t"));
			s = f.next("\t");
			//if(i!=0) s = s.substr(1);
			std::cout<<"processing: '"<<s<<"'... ";
			switch(coltypes[i]){
			case COL_DEVICE:
				std::cout<<"device"<<std::endl;
				s_device = s;
				break;
			case COL_VALUE:
				std::cout<<"value"<<std::endl;
				s_value = s;
				break;
			case COL_FOOTPRINT:
				std::cout<<"footprint"<<std::endl;
				s_footprint = s;
				break;
			case COL_QUANTITY:
				std::cout<<"quantity"<<std::endl;
				s_quantity = s;
				break;
			case COL_REFDES:
				std::cout<<"refdes"<<std::endl;
				s_refdes = s;
				break;
			case COL_UNKNOWN:
				std::cout<<"unknown"<<std::endl;
				break;
			}
		}

		/*std::cout<<"s_device="<<s_device<<std::endl;
		std::cout<<"s_value="<<s_value<<std::endl;
		std::cout<<"s_footprint="<<s_footprint<<std::endl;
		std::cout<<"s_quantity="<<s_quantity<<std::endl;
		std::cout<<"s_refdes="<<s_refdes<<std::endl;*/

		wxString device_wx(s_device.c_str(), wxConvUTF8);
		wxString value_wx(s_value.c_str(), wxConvUTF8);
		wxString footprint_wx(s_footprint.c_str(), wxConvUTF8);
		//wxString quantity_wx(s_quantity, wxConvUTF8);

		//std::vector<std::string> refdeses;
		strfnd f2(s_refdes);
		while(!f2.atend()){
			//std::string refdes = trim(f2.next(" "));
			std::string refdes = f2.next(" ");
			std::cout<<"adding component: refdes="<<refdes<<std::endl;
			//std::cout<<"refdes="<<refdes<<std::endl;
			wxString refdes_wx(refdes.c_str(), wxConvUTF8);
			NetComponent *c = new NetComponent;
			c->m_refdes = refdes_wx;
			c->m_value = value_wx;
			c->m_footprint = footprint_wx;
			c->m_device = device_wx;
			m_components.push_back(c);
		}
	}

	file.close();

	SortNets();

	return true;
}

bool SortNetsComp(Net *net1, Net *net2)
{
	bool bn1 = net1->m_name.IsNumber();
	bool bn2 = net2->m_name.IsNumber();
	if(!bn1 && bn2) return true;
	else if(bn1 && !bn2) return false;
	if(bn1 && bn2){
		long l1, l2;
		net1->m_name.ToLong(&l1);
		net2->m_name.ToLong(&l2);
		return l1 < l2;
	}
	return net1->m_name < net2->m_name;
}

void Netlist::SortNets()
{
	sort(m_nets.begin(), m_nets.end(), SortNetsComp);
}

void Netlist::ValidateNets(Board *board)
{
	//loop through nets
	for(std::vector<Net*>::iterator i = m_nets.begin();
			i != m_nets.end(); i++){
		Net *net = (*i);
		net->m_validity = NET_VALID;
		//loop through netpins
		for(std::list<NetPin>::iterator i=net->m_pins.begin(); i!=net->m_pins.end();i++){
			//get pin place on board
			BoardHPoint place = board->FindNetPinPlace(*i);
			//if pin is not found on the board, net is invalid
			if(!place.exists){
				net->m_validity = NET_LACKS_PINS;
				break;
			}
			//get all connected points
			std::set<int> points;
			board->GetConnected(place, points);
			//loop through points
			unsigned int number_of_right_netpins_found = 0;
			bool invalid_pin_found = false;
			for(std::set<int>::iterator j= points.begin(); j != points.end(); j++){
				BoardHPoint hp = board->IntPointToBoardHPoint(*j);
				//get netpin at point
				NetPin fpin;
				if(!board->GetNetPinByPoint(hp, &fpin)){
					//no pin at this point
					continue;
				}
				if(GetNetByPin(fpin) != net){
					//if netpin is not found in the net, net is invalid
					invalid_pin_found = true;
					break;
				}
				number_of_right_netpins_found++;
			}
			if(invalid_pin_found){
				net->m_validity = NET_INVALID_PIN;
				break;
			}
			if(number_of_right_netpins_found < net->m_pins.size()){
				net->m_validity = NET_LACKS_PINS;
				break;
			}
			if(number_of_right_netpins_found > net->m_pins.size()){
				net->m_validity = NET_INVALID_PIN;
				break;
			}
		}
	}
}


